home *** CD-ROM | disk | FTP | other *** search
- /*
- * readfile.c - Pcal routines concerned with reading and parsing the datefile
- *
- * Contents:
- *
- * cleanup
- * clear_syms
- * date_type
- * do_define
- * do_ifdef
- * do_ifndef
- * do_include
- * do_undef
- * enter_day_info
- * find_easter
- * find_sym
- * find_year
- * get_holiday
- * get_keywd
- * get_month
- * get_ordinal
- * get_phase
- * get_prep
- * get_token
- * get_weekday
- * is_anyday
- * is_fquarter
- * is_fullmoon
- * is_holiday
- * is_lquarter
- * is_newmoon
- * is_weekday
- * is_workday
- * not_holiday
- * not_weekday
- * not_workday
- * parse
- * parse_date
- * parse_ord
- * parse_rel
- * read_datefile
- *
- * Revision history:
- *
- * 4.5 AWR 11/08/93 accept European dates of form "dd. mm."
- * and "dd. mon"
- *
- * 04/28/93 restructure function definitions so
- * function name appears in first column
- * (to facilitate searching for definition
- * by name)
- *
- * 03/05/93 Propagate null text to output
- *
- * 02/11/92 Support predefined holidays (strings
- * and dispatch functions - cf. pcallang.h)
- *
- * 4.4 AWR 01/20/92 Use predominant color ("logical black")
- * in is_weekday()
- *
- * 12/16/91 Avoid invalid array access in
- * read_datefile()
- *
- * 4.3 AWR 10/25/91 Support moon phase wildcards and
- * -Z flag (debug information)
- *
- * 4.2 AWR 10/03/91 Support "note/<n>" (user-selected
- * notes box) as per Geoff Kuenning
- *
- * 09/30/91 Support "elif" in datefile
- *
- * 4.11 AWR 08/20/91 Support "nearest" keyword as per
- * Andy Fyfe
- *
- * 4.0 AWR 02/19/91 Support negative ordinals
- *
- * 02/06/91 Support expressions in "if{n}def"
- *
- * 02/04/91 Support "even" and "odd" ordinals
- * and ordinals > 5th; support "year"
- *
- * 01/15/91 Extracted from pcal.c
- *
- */
-
- /*
- * Standard headers:
- */
-
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
-
- /*
- * Pcal-specific definitions:
- */
-
- #include "pcaldefs.h"
- #include "pcalglob.h"
- #include "pcallang.h"
-
- /*
- * Macros:
- */
-
- /* status codes returned by parse(), enter_day_info() */
- #define PARSE_OK 0 /* successful date parse */
- #define PARSE_INVDATE 1 /* nonexistent date */
- #define PARSE_INVLINE 2 /* syntax error */
- #define PARSE_NOMATCH 3 /* no match for wildcard */
-
- /* codes for states in read_datefile() */
- #define PROCESSING 0 /* currently processing datefile lines */
- #define AWAITING_TRUE 1 /* awaiting first TRUE branch in "if{n}def" */
- #define SKIP_TO_ENDIF 2 /* finished processing first TRUE branch */
-
- #define BLANK_TEXT " " /* substitute for null input text */
-
- /* append date to list; terminate list */
- #define ADD_DATE(_m, _d, _y) if (1) { \
- if (DEBUG(DEBUG_DATES)) \
- FPR(stderr, "matched %d/%d/%d\n", \
- _m, _d, _y); \
- pdate->mm = _m, pdate->dd = _d, pdate++->yy = _y; \
- } else
-
- #define TERM_DATES pdate->mm = pdate->dd = pdate->yy = 0
-
- /*
- * Globals:
- */
-
- static DATE dates[MAX_DATES+1]; /* array of date structures */
- static char *pp_sym[MAX_PP_SYMS]; /* preprocessor defined symbols */
-
-
- /*
- * read_datefile - read and parse date file, handling preprocessor lines
- *
- * This is the main routine of this module. It calls getline() to read each
- * non-null line (stripped of leading blanks and trailing comments), loadwords()
- * to "tokenize" it, and get_token() to classify it as a preprocessor directive
- * or "other". A switch{} statement takes the appropriate action for each
- * token type; "other" lines are further classified by parse() (q.v.) which
- * calls parse_date() (q.v.) to parse date entries and enter them in the data
- * structure (as described in pcaldefs.h).
- *
- */
- void
- #ifdef PROTOS
- read_datefile(FILE *fp, /* file pointer (assumed open) */
- char *filename) /* file name (for error messages) */
- #else
- read_datefile(fp, filename)
- FILE *fp; /* file pointer (assumed open) */
- char *filename; /* file name (for error messages) */
- #endif
- {
- static int file_level = 0;
- int if_level = 0;
- int line = 0;
-
- int pptype, extra, ntokens, save_year, expr;
- int (*pfcn)();
- char *ptok;
- char **pword;
- char msg[STRSIZ], incpath[STRSIZ];
-
- /* stack for processing nested "if{n}defs" - required for "elif" */
- struct {
- int state; /* PROCESSING, AWAITING_TRUE, SKIP_TO_ENDIF */
- int else_ok; /* is "elif" or "else" legal at this point? */
- } if_state[MAX_IF_NESTING+1];
-
- if (fp == NULL) /* whoops, no date file */
- return;
-
- /* note that there is no functional limit on file nesting; this is
- * mostly to catch infinite loops (e.g., a includes b, b includes a)
- */
- if (++file_level > MAX_FILE_NESTING) {
- ERR(E_FILE_NESTING);
- exit(EXIT_FAILURE);
- }
-
- save_year = curr_year; /* save default year */
-
- if_state[0].state = PROCESSING; /* set up initial state */
- if_state[0].else_ok = FALSE;
-
- /* read lines until EOF */
-
- while (getline(fp, lbuf, &line)) {
- char suffix;
-
- if (DEBUG(DEBUG_PP)) {
- FPR(stderr, "%s (%d)", filename, line);
- if (if_state[if_level].state == PROCESSING)
- FPR(stderr, ": '%s'", lbuf);
- FPR(stderr, "\n");
- }
-
- ntokens = loadwords(words, lbuf); /* split line into tokens */
- pword = words; /* point to the first */
-
- /* get token type and pointers to function and name */
-
- suffix = words[0][strlen(words[0])-1];
- pptype = get_token(*pword++);
- pfcn = pptype == PP_OTHER ? NULL : pp_info[pptype].pfcn;
- ptok = pptype == PP_OTHER ? "" : pp_info[pptype].name;
-
- switch (pptype) {
-
- case PP_DEFINE:
- case PP_UNDEF:
- if (if_state[if_level].state == PROCESSING)
- (void) (*pfcn)(*pword);
- extra = ntokens > 2;
- break;
-
- case PP_ELIF:
- if (!if_state[if_level].else_ok) {
- ERR(E_ELIF_ERR);
- break;
- }
-
- /* if a true expression has just been processed, disable
- * processing and skip to endif; if no true expression
- * has been found yet and the current expression is
- * true, enable processing
- */
- switch (if_state[if_level].state) {
- case PROCESSING:
- if_state[if_level].state = SKIP_TO_ENDIF;
- break;
- case AWAITING_TRUE:
- copy_text(lbuf, pword); /* reconstruct string */
- if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
- ERR(E_EXPR_SYNTAX);
- expr = FALSE;
- }
- if (expr)
- if_state[if_level].state = PROCESSING;
- break;
- }
-
- extra = FALSE;
- break;
-
- case PP_ELSE:
- if (!if_state[if_level].else_ok) {
- ERR(E_ELSE_ERR);
- break;
- }
-
- /* if a true condition has just been processed, disable
- * processing and skip to endif; if no true condition
- * has been found yet, enable processing
- */
- switch (if_state[if_level].state) {
- case PROCESSING:
- if_state[if_level].state = SKIP_TO_ENDIF;
- break;
- case AWAITING_TRUE:
- if_state[if_level].state = PROCESSING;
- break;
- }
-
- /* subsequent "elif" or "else" forbidden */
- if_state[if_level].else_ok = FALSE;
- extra = ntokens > 1;
- break;
-
- case PP_ENDIF:
- if (if_level < 1) {
- ERR(E_END_ERR);
- break;
- }
- if_level--;
- extra = ntokens > 1;
- break;
-
- case PP_IFDEF:
- case PP_IFNDEF:
- /* "if{n}def"s nested too deeply? */
- if (++if_level > MAX_IF_NESTING) {
- ERR(E_IF_NESTING);
- exit(EXIT_FAILURE);
- break;
- }
-
- /* if processing enabled at outer level, evaluate
- * expression and enable/disable processing for
- * following clause; if not, skip to matching endif
- */
- if (if_state[if_level-1].state == PROCESSING) {
- copy_text(lbuf, pword); /* reconstruct string */
- if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
- ERR(E_EXPR_SYNTAX);
- expr = FALSE;
- }
- if_state[if_level].state = expr ? PROCESSING :
- AWAITING_TRUE;
- } else
- if_state[if_level].state = SKIP_TO_ENDIF;
-
- if_state[if_level].else_ok = TRUE;
- extra = FALSE;
- break;
-
- case PP_INCLUDE:
- if (if_state[if_level].state == PROCESSING)
- do_include(mk_path(incpath, filename), *pword,
- suffix == '?');
- extra = ntokens > 2;
- break;
-
- case PP_OTHER: /* none of the above - parse as date */
- if (if_state[if_level].state == PROCESSING) {
- switch (parse(words, filename)) {
- case PARSE_INVDATE:
- ERR(E_INV_DATE);
- break;
-
- case PARSE_INVLINE:
- ERR(E_INV_LINE);
- break;
-
- case PARSE_NOMATCH:
- ERR(E_NO_MATCH);
- break;
- }
- }
- extra = FALSE;
- break;
-
- } /* end switch */
-
- if (extra) { /* extraneous data? */
- sprintf(msg, E_GARBAGE, ptok);
- ERR(msg);
- }
-
- } /* end while */
-
- if (if_level > 0)
- FPR(stderr, E_UNT_IFDEF, progname, filename);
-
- file_level--;
- curr_year = save_year; /* restore default year */
- }
-
-
- /*
- * Routines to free allocated data (symbol table and data structure)
- */
-
-
- /*
- * clear_syms - clear and deallocate the symbol table
- */
- void
- #ifdef PROTOS
- clear_syms(void)
- #else
- clear_syms()
- #endif
- {
- int i;
-
- for (i = 0; i < MAX_PP_SYMS; i++)
- if (pp_sym[i]) {
- free(pp_sym[i]);
- pp_sym[i] = NULL;
- }
- }
-
-
- /*
- * cleanup - free all allocated data
- */
- void
- #ifdef PROTOS
- cleanup(void)
- #else
- cleanup()
- #endif
- {
- int i, j;
- year_info *py, *pny;
- month_info *pm;
- day_info *pd, *pnd;
-
- for (py = head; py; py = pny) { /* main data structure */
- pny = py->next;
- for (i = 0; i < 12; i++) {
- if ((pm = py->month[i]) == NULL)
- continue;
- for (j = 0; j < LAST_NOTE_DAY; j++)
- for (pd = pm->day[j]; pd; pd = pnd) {
- pnd = pd->next;
- free(pd->text);
- free(pd);
- }
- free(pm);
- }
- free(py);
- }
-
- clear_syms(); /* symbol table */
-
- }
-
-
- /*
- * Preprocessor token and symbol table routines
- */
-
-
- /*
- * find_sym - look up symbol; return symbol table index if found, PP_SYM_UNDEF
- * if not found
- */
- int
- #ifdef PROTOS
- find_sym(char *sym)
- #else
- find_sym(sym)
- char *sym;
- #endif
- {
- int i;
-
- if (!sym)
- return PP_SYM_UNDEF;
-
- for (i = 0; i < MAX_PP_SYMS; i++)
- if (pp_sym[i] && ci_strcmp(pp_sym[i], sym) == 0)
- return i;
-
- return PP_SYM_UNDEF;
- }
-
-
- /*
- * do_ifdef - return TRUE if 'expr' is true; FALSE if not; EXPR_ERR if invalid
- */
- int
- #ifdef PROTOS
- do_ifdef(char *expr)
- #else
- do_ifdef(expr)
- char *expr;
- #endif
- {
- return parse_expr(expr);
- }
-
-
- /*
- * do_ifndef - return FALSE if 'expr' is true; TRUE if not; EXPR_ERR if invalid
- */
- int
- #ifdef PROTOS
- do_ifndef(char *expr)
- #else
- do_ifndef(expr)
- char *expr;
- #endif
- {
- int val;
-
- return (val = parse_expr(expr)) == EXPR_ERR ? EXPR_ERR : ! val;
- }
-
-
- /*
- * do_define - enter 'sym' into symbol table; if 'sym' NULL, clear symbol table.
- * Always returns 0 (for compatibility with other dispatch functions).
- */
- int
- #ifdef PROTOS
- do_define(char *sym)
- #else
- do_define(sym)
- char *sym;
- #endif
- {
- int i;
-
- if (! sym) { /* null argument - clear all definitions */
- clear_syms();
- return 0;
- }
-
- if (do_ifdef(sym)) /* already defined? */
- return 0;
-
- for (i = 0; i < MAX_PP_SYMS; i++) /* find room for it */
- if (! pp_sym[i]) {
- strcpy(pp_sym[i] = alloc(strlen(sym)+1), sym);
- return 0;
- }
-
- FPR(stderr, E_SYMFULL, progname, sym);
- return 0;
- }
-
-
- /*
- * do_undef - undefine 'sym' and free its space; no error if not defined.
- * Always return 0 (for compatibility with other dispatch fcns).
- */
- int
- #ifdef PROTOS
- do_undef(char *sym)
- #else
- do_undef(sym)
- char *sym;
- #endif
- {
- int i;
-
- if (! sym)
- return 0;
-
- if ((i = find_sym(sym)) != PP_SYM_UNDEF) {
- free(pp_sym[i]);
- pp_sym[i] = NULL;
- }
-
- return 0;
- }
-
-
- /*
- * do_include - include specified file (optionally in "" or <>); always
- * returns 0 (for compatibility with related functions returning int)
- */
- int
- #ifdef PROTOS
- do_include(char *path,
- char *name,
- int noerr)
- #else
- do_include(path, name, noerr)
- char *path; /* path to file */
- char *name; /* file name */
- int noerr; /* ignore nonexistent file */
- #endif
- {
- FILE *fp;
- char *p, incfile[STRSIZ], tmpnam[STRSIZ];
-
- if (! name) /* whoops, no date file */
- return 0;
-
- /* copy name, stripping "" or <> */
- strcpy(tmpnam, name + (*name == '"' || *name == '<'));
- if ((p = P_LASTCHAR(tmpnam)) && *p == '"' || *p == '>')
- *p = '\0';
-
- /* special hack - replace %y with last two digits of curr_year */
- while ((p = strchr(tmpnam, '%')) != NULL && *(p+1) == 'y') {
- *p++ = (curr_year / 10) % 10 + '0';
- *p = curr_year % 10 + '0';
- }
-
- if ((fp = fopen(mk_filespec(incfile, path, tmpnam), "r")) == NULL) {
- if (noerr)
- return 0;
- FPR(stderr, E_FOPEN_ERR, progname, incfile);
- exit(EXIT_FAILURE);
- }
-
- read_datefile(fp, incfile); /* recursive call */
- fclose(fp);
-
- return 0;
- }
-
-
-
- /*
- * Dispatch functions for wildcard matching
- */
-
-
- /*
- * is_anyday - dummy function which always returns TRUE
- */
- int
- #ifdef PROTOS
- is_anyday(int mm,
- int dd,
- int yy)
- #else
- is_anyday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return TRUE;
- }
-
-
- /*
- * is_weekday - determine whether or not mm/dd/yy is a weekday (i.e., the
- * day of the week normally prints in "logical black", the most prevalent
- * color)
- */
- int
- #ifdef PROTOS
- is_weekday(int mm,
- int dd,
- int yy)
- #else
- is_weekday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return day_color[calc_weekday(mm, dd, yy)] == weekday_color;
- }
-
-
- /*
- * is_workday - determine whether or not mm/dd/yy is a workday (i.e., the
- * day of the week normally prints in black and the date is not a holiday)
- */
- int
- #ifdef PROTOS
- is_workday(int mm,
- int dd,
- int yy)
- #else
- is_workday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return is_weekday(mm, dd, yy) && ! is_holiday(mm, dd, yy);
- }
-
-
- /*
- * is_holiday - determine whether or not mm/dd/yy is a holiday
- */
- int
- #ifdef PROTOS
- is_holiday(int mm,
- int dd,
- int yy)
- #else
- is_holiday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- year_info *py;
- month_info *pm;
-
- pm = (py = find_year(yy, FALSE)) ? py->month[mm-1] : NULL;
- return pm ? (pm->holidays & (1L << (dd-1))) != 0 : FALSE;
- }
-
-
- /*
- * not_XXXXX - converses of is_XXXXX above
- */
- int
- #ifdef PROTOS
- not_weekday(int mm,
- int dd,
- int yy)
- #else
- not_weekday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return !is_weekday(mm, dd, yy);
- }
-
-
- int
- #ifdef PROTOS
- not_workday(int mm,
- int dd,
- int yy)
- #else
- not_workday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return !is_workday(mm, dd, yy);
- }
-
-
- int
- #ifdef PROTOS
- not_holiday(int mm,
- int dd,
- int yy)
- #else
- not_holiday(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- return !is_holiday(mm, dd, yy);
- }
-
-
- /*
- * is_newmoon - determine whether or not mm/dd/yy is the date of a new moon
- */
- int
- #ifdef PROTOS
- is_newmoon(int mm,
- int dd,
- int yy)
- #else
- is_newmoon(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- int quarter;
-
- (void) find_phase(mm, dd, yy, &quarter);
- return quarter == MOON_NM;
- }
-
-
- /*
- * is_firstq - determine whether or not mm/dd/yy is the date of a first quarter
- */
- int
- #ifdef PROTOS
- is_firstq(int mm,
- int dd,
- int yy)
- #else
- is_firstq(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- int quarter;
-
- (void) find_phase(mm, dd, yy, &quarter);
- return quarter == MOON_1Q;
- }
-
-
- /*
- * is_fullmoon - determine whether or not mm/dd/yy is the date of a full moon
- */
- int
- #ifdef PROTOS
- is_fullmoon(int mm,
- int dd,
- int yy)
- #else
- is_fullmoon(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- int quarter;
-
- (void) find_phase(mm, dd, yy, &quarter);
- return quarter == MOON_FM;
- }
-
-
- /*
- * is_lastq - determine whether or not mm/dd/yy is the date of a last quarter
- */
- int
- #ifdef PROTOS
- is_lastq(int mm,
- int dd,
- int yy)
- #else
- is_lastq(mm, dd, yy)
- int mm;
- int dd;
- int yy;
- #endif
- {
- int quarter;
-
- (void) find_phase(mm, dd, yy, &quarter);
- return quarter == MOON_3Q;
- }
-
-
-
- /*
- * Routines to find predefined holidays too complicated to express as Pcal
- * date strings. All add the matching date(s) (yes, holidays which span
- * multiple days are allowed) to the dates[] array (pointed to by pdate)
- * and return the number of matching dates.
- */
-
-
- /*
- * find_easter - find Easter of current year; add to date array (adapted
- * by parties unknown from Knuth's _The Art of Computer Programming_, v. 1)
- */
-
- #define METONIC 19 /* length of metonic cycle */
-
- int
- #ifdef PROTOS
- find_easter(DATE *pdate) /* pointer into date array */
- #else
- find_easter(pdate)
- DATE *pdate; /* pointer into date array */
- #endif
- {
- /* Easter is defined as the Sunday after the full moon on or
- * after March 21. You could express that as "1st Sun after
- * 1st full_moon ooa 21st day in March" instead of using this
- * routine, but depending on your timezone your full moon might
- * not fall on the same day as the "official" one.
- */
-
- DATE *sv_pdate = pdate;
- register int epact, fm;
- int golden, century, nleap;
-
- golden = curr_year % METONIC + 1;
- century = curr_year / 100 + 1;
- nleap = 3 * century / 4 - 12;
-
- /* correct for moon's orbit */
- epact = (11 * golden + 20 + (8 * century + 5) / 25 - 5 - nleap) % 30;
- if (epact < 0)
- epact += 30;
- if (epact == 25 && golden > 11 || epact == 24)
- ++epact;
-
- /* find full moon on or after 3/21 */
- fm = 44 - epact;
- if (fm < 21)
- fm += 30;
-
- /* find date of following Sunday */
- fm += 7 - ((int)(5L * curr_year / 4) - nleap - 10 + fm) % 7;
-
- /* add Easter to date array (adjust if in April) */
- ADD_DATE(fm <= 31 ? MAR : APR, fm <= 31 ? fm : fm - 31, curr_year);
-
- /* return number of dates added (always 1 in this routine) */
- return pdate - sv_pdate;
- }
-
- /*
- * Keyword classification routines
- */
-
- /*
- * get_month - convert alpha (or, optionally, numeric) string to month; return
- * 1..12 if valid, NOT_MONTH if not, ALL_MONTHS if "all", ENTIRE_YEAR if "year"
- */
- int
- #ifdef PROTOS
- get_month(char *cp, /* string to convert */
- int numeric_ok, /* accept numeric string ? */
- int year_ok) /* accept "year"? */
- #else
- get_month(cp, numeric_ok, year_ok)
- char *cp; /* string to convert */
- int numeric_ok; /* accept numeric string ? */
- int year_ok; /* accept "year"? */
- #endif
- {
- int mm;
-
- if (! cp)
- return NOT_MONTH;
-
- if (get_keywd(cp) == DT_ALL)
- return ALL_MONTHS;
-
- if (year_ok && get_keywd(cp) == DT_YEAR)
- return ENTIRE_YEAR;
-
- if (numeric_ok && isdigit(*cp))
- mm = atoi(cp);
- else
- for (mm = JAN;
- mm <= DEC && ci_strncmp(cp, months[mm-1], MIN_MONTH_LEN);
- mm++)
- ;
-
- return mm >= JAN && mm <= DEC ? mm : NOT_MONTH;
- }
-
-
- /*
- * get_weekday - look up string in weekday list; return 0-6 if valid,
- * NOT_WEEKDAY if not. If wild_ok flag is set, accept "day", "weekday",
- * "workday", "holiday", or moon phase and return appropriate value.
- */
- int
- #ifdef PROTOS
- get_weekday(char *cp,
- int wild_ok)
- #else
- get_weekday(cp, wild_ok)
- char *cp;
- int wild_ok;
- #endif
- {
- int w;
-
- if (!cp)
- return NOT_WEEKDAY;
-
- if (wild_ok) { /* try wildcards first */
- for (w = WILD_FIRST_WKD; w <= WILD_LAST_WKD; w++)
- if (ci_strncmp(cp, days[w], strlen(days[w])) == 0)
- return w;
- if ((w = get_phase(cp)) != MOON_OTHER)
- return w + WILD_FIRST_MOON;
- }
-
- for (w = SUN; w <= SAT; w++)
- if (ci_strncmp(cp, days[w], MIN_DAY_LEN) == 0)
- return w;
-
- return NOT_WEEKDAY;
- }
-
-
- /*
- * get_keywd - look up string in misc. keyword list; return keyword code
- * if valid, DT_OTHER if not
- */
- int
- #ifdef PROTOS
- get_keywd(char *cp)
- #else
- get_keywd(cp)
- char *cp;
- #endif
- {
- KWD *k;
-
- if (!cp)
- return DT_OTHER;
-
- for (k = keywds;
- k->name && ci_strncmp(cp, k->name, strlen(k->name));
- k++)
- ;
-
- return k->code;
- }
-
-
- /*
- * get_ordinal - look up string in ordinal list; return ordinal code (and
- * fill in ordinal value) if valid, return ORD_OTHER if not
- */
- int
- #ifdef PROTOS
- get_ordinal(char *cp,
- int *pval)
- #else
- get_ordinal(cp, pval)
- char *cp;
- int *pval;
- #endif
- {
- KWD_O *o;
- int val;
- char **psuf;
-
- if (!cp)
- return ORD_OTHER;
-
- if (isdigit(*cp) || *cp == '-') { /* numeric? */
- if ((val = atoi(cp)) == 0)
- return ORD_OTHER;
-
- if (*cp == '-') /* skip over number */
- cp++;
- cp += strspn(cp, DIGITS);
-
- for (psuf = ord_suffix; *psuf; psuf++) /* find suffix */
- if (ci_strcmp(cp, *psuf) == 0) {
- *pval = val;
- return val < 0 ? ORD_NEGNUM : ORD_POSNUM;
- }
-
- return ORD_OTHER;
- }
-
- /* look for word in ordinals list */
-
- for (o = ordinals; o->name && ci_strncmp(cp, o->name, MIN_ORD_LEN); o++)
- ;
-
- *pval = o->value;
- return o->code;
- }
-
-
- /*
- * get_phase - convert moon phase string to appropriate value
- */
- int
- #ifdef PROTOS
- get_phase(char *cp)
- #else
- get_phase(cp)
- char *cp;
- #endif
- {
- KWD *p;
-
- if (!cp)
- return MOON_OTHER;
-
- for (p = phases; p->name && ci_strcmp(cp, p->name); p++)
- ;
-
- return p->code;
- }
-
-
- /*
- * get_prep - look up string in preposition list; return preposition code if
- * valid, PR_OTHER if not
- */
- int
- #ifdef PROTOS
- get_prep(char *cp)
- #else
- get_prep(cp)
- char *cp;
- #endif
- {
- KWD *p;
-
- if (!cp)
- return PR_OTHER;
-
- for (p = preps; p->name && ci_strncmp(cp, p->name, MIN_PREP_LEN); p++)
- ;
-
- return p->code;
- }
-
-
- /*
- * get_token - look up 'token' in list of preprocessor tokens; return its
- * code if found, PP_OTHER if not
- */
- int
- #ifdef PROTOS
- get_token(char *token)
- #else
- get_token(token)
- char *token;
- #endif
- {
- KWD_F *p;
-
- for (p = pp_info;
- p->name && ci_strncmp(token, p->name, MIN_PPTOK_LEN);
- p++)
- ;
-
- return p->code;
- }
-
-
- /*
- * get_holiday - look up string in holiday list; return its index if found,
- * NOT_HOLIDAY if not
- */
- int
- #ifdef PROTOS
- get_holiday(char *cp)
- #else
- get_holiday(cp)
- char *cp;
- #endif
- {
- KWD_H *p;
- char tmp[STRSIZ];
-
- strcpy(tmp, cp); /* make copy without any trailing '*' */
- if (LASTCHAR(tmp) == '*')
- LASTCHAR(tmp) = '\0';
-
- for (p = holidays; p->name; p++)
- if (ci_strcmp(tmp, p->name) == 0)
- return p - holidays;
-
- return NOT_HOLIDAY;
- }
-
-
- /*
- * date_type - examine token and return date type code; if DT_MONTH, DT_ORDINAL,
- * or DT_WEEKDAY, fill in appropriate code (and value if DT_ORDINAL)
- */
- int
- #ifdef PROTOS
- date_type(char *cp, /* pointer to start of token */
- int *pn, /* token type code (returned) */
- int *pv) /* ordinal value (returned) */
- #else
- date_type(cp, pn, pv)
- char *cp; /* pointer to start of token */
- int *pn; /* token type code (returned) */
- int *pv; /* ordinal value (returned) */
- #endif
- {
- int n, v;
-
- /* look for weekdays first, to catch wildcards "1q", "3q", etc. */
- if ((n = get_weekday(cp, TRUE)) != NOT_WEEKDAY) /* weekday name? */
- return (*pn = n, DT_WEEKDAY);
-
- if ((n = get_holiday(cp)) != NOT_HOLIDAY) /* holiday? */
- return (*pn = n, DT_HOLIDAY);
-
- if ((n = get_ordinal(cp, &v)) != ORD_OTHER) /* ordinal? */
- return (*pn = n, *pv = v, DT_ORDINAL);
-
- if (isdigit(*cp)) /* other digit? */
- return (IS_NUMERIC(cp) ||
- (date_style == EUR_DATES && IS_EURDATE(cp))) ?
- DT_EURDATE : DT_DATE;
-
- /* "all" can be either a keyword or a month wildcard - look for
- the former usage first */
-
- if ((n = get_keywd(cp)) != DT_OTHER)
- return n;
-
- if ((n = get_month(cp, FALSE, FALSE)) != NOT_MONTH) /* month name? */
- return (*pn = n, DT_MONTH);
-
- return DT_OTHER; /* unrecognized keyword - give up */
-
- }
-
-
-
- /*
- * Routines for entering data in the data structure (described in pcaldefs.h)
- */
-
-
- /*
- * find_year - find record in year list; optionally create if not present
- */
- year_info *
- #ifdef PROTOS
- find_year(int year,
- int insert) /* insert if missing */
- #else
- find_year(year, insert)
- int year;
- int insert; /* insert if missing */
- #endif
- {
- year_info *pyear, *plast, *p;
-
- for (plast = NULL, pyear = head; /* search linked list */
- pyear && pyear->year < year;
- plast = pyear, pyear = pyear->next)
- ;
-
- if (pyear && pyear->year == year) /* found - return it */
- return pyear;
-
- if (insert) { /* not found - insert it if requested */
- p = (year_info *) alloc((int) sizeof(year_info)); /* create new record */
- p->year = year;
-
- p->next = pyear; /* link it in */
- return *(plast ? &plast->next : &head) = p;
- }
- else
- return NULL;
- }
-
-
- /*
- * enter_day_info - enter text for specified day; avoid entering duplicates.
- * Returns PARSE_INVDATE if date invalid, PARSE_OK if OK; if symbol FEB_29_OK
- * is non-zero (cf. pcaldefs.h), will silently ignore Feb 29 of common year.
- */
- int
- #ifdef PROTOS
- enter_day_info(int m,
- int d,
- int y,
- int text_type,
- char **pword)
- #else
- enter_day_info(m, d, y, text_type, pword)
- int m, d, y;
- int text_type;
- char **pword;
- #endif
- {
- static year_info *pyear;
- static int prev_year = 0;
- month_info *pmonth;
- day_info *pday, *plast;
- int is_holiday = text_type == HOLIDAY_TEXT;
- char text[LINSIZ];
-
- if (! is_valid(m, d >= FIRST_NOTE_DAY && text_type == NOTE_TEXT ? 1 : d, y))
- return (m == FEB && d == 29 && FEB_29_OK) ? PARSE_OK : PARSE_INVDATE;
-
- if (y != prev_year) /* avoid unnecessary year lookup */
- pyear = find_year(y, 1);
-
- --m, --d; /* adjust for use as subscripts */
-
- if ((pmonth = pyear->month[m]) == NULL) /* find/create month record */
- pyear->month[m] = pmonth = (month_info *) alloc((int) sizeof(month_info));
-
- if (is_holiday)
- pmonth->holidays |= (1L << d);
-
- /* insert text for day at end of list (preserving the order of entry
- * for multiple lines on same day); eliminate those differing only
- * in spacing and capitalization from existing entries
- */
-
- copy_text(text, pword); /* consolidate text from lbuf into text */
-
- if (DEBUG(DEBUG_DATES)) {
- char *p;
- FPR(stderr, "%02d/%02d/%d%c '", m+1, d+1, y,
- is_holiday ? '*' : ' ');
- for (p = text; *p; p++)
- FPR(stderr, isprint(*p) ? "%c" : "\\%03o", *p & CHAR_MSK);
- FPR(stderr, "'\n");
- }
-
- #if KEEP_NULL_LINES /* preserve blank text lines in output */
- if (*text == '\0' && pmonth->day[d])
- strcpy(text, BLANK_TEXT);
- #endif
-
- /* check that non-null text is unique */
-
- if (*text) {
- for (plast = NULL, pday = pmonth->day[d];
- pday;
- plast = pday, pday = pday->next)
- if (ci_strcmp(pday->text, text) == 0
- #if KEEP_NULL_LINES
- && strcmp(text, BLANK_TEXT) != 0
- #endif
- ) {
- pday->is_holiday |= is_holiday;
- return PARSE_OK;
- }
-
- /* unique - add to end of list */
-
- pday = (day_info *) alloc(sizeof(day_info));
- pday->is_holiday = is_holiday;
- strcpy(pday->text = (char *) alloc(strlen(text)+1), text);
- pday->next = NULL;
- *(plast ? &plast->next : &pmonth->day[d]) = pday;
- }
-
- return PARSE_OK;
- }
-
-
-
- /*
- * Date parsing routines:
- */
-
-
- /*
- * parse_ord - parse an ordinal date spec (e.g. "first Monday in September",
- * "every Sunday in October", "last workday in all"); return PARSE_OK if line
- * syntax valid, PARSE_INVLINE if not. Write all matching dates (if any) to
- * global array dates[]; terminate date list with null entry.
- */
- int
- #ifdef PROTOS
- parse_ord(int ord, /* valid ordinal code - from get_ordinal() */
- int val, /* ordinal value - also from get_ordinal() */
- char **pword) /* pointer to word after ordinal */
- #else
- parse_ord(ord, val, pword)
- int ord; /* valid ordinal code - from get_ordinal() */
- int val; /* ordinal value - also from get_ordinal() */
- char **pword; /* pointer to word after ordinal */
- #endif
- {
- int wkd, mon, mm, dd, len, (*pfcn)(), doit;
- int val_first, val_last, val_incr, mon_first, mon_last;
- DATE *pdate, date;
-
- if ((wkd = get_weekday(*pword, TRUE)) == NOT_WEEKDAY || /* weekday */
- *++pword == NULL || /* any word */
- (mon = get_month(*++pword, FALSE, TRUE)) == NOT_MONTH) /* month */
- return PARSE_INVLINE;
-
- /* set up loop boundaries for month loop */
- mon_first = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? JAN : mon;
- mon_last = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? DEC : mon;
-
- pdate = dates; /* start of date array */
-
- /* special case of "all|odd|even <wildcard> in <month>|all|year" */
-
- if ((ord == ORD_ALL || ord == ORD_EVEN || ord == ORD_ODD) &&
- IS_WILD(wkd)) {
- pfcn = pdatefcn[wkd - WILD_FIRST];
- doit = ord != ORD_EVEN;
- for (mm = mon_first; mm <= mon_last; mm++) {
- len = LENGTH_OF(mm, curr_year);
- if (mon != ENTIRE_YEAR)
- doit = ord != ORD_EVEN;
- for (dd = 1; dd <= len; dd++)
- if ((*pfcn)(mm, dd, curr_year)) {
- if (doit)
- ADD_DATE(mm, dd, curr_year);
- if (ord != ORD_ALL)
- doit = ! doit;
- }
- }
- }
-
- /* special case of "odd|even <weekday> in year" */
-
- else if ((ord == ORD_EVEN || ord == ORD_ODD) && mon == ENTIRE_YEAR) {
- date.mm = JAN; /* starting date */
- date.dd = calc_day(ord == ORD_EVEN ? 2 : 1, wkd, JAN);
- date.yy = curr_year;
- do { /* alternates throughout year */
- ADD_DATE(date.mm, date.dd, date.yy);
- date.dd += 14;
- normalize(&date);
- } while (date.yy == curr_year);
- }
-
- /* special case of "<ordinal>|last <weekday>|<wildcard> in year" */
-
- else if ((ord == ORD_NEGNUM || ord == ORD_POSNUM) &&
- mon == ENTIRE_YEAR) {
- if (calc_year_day(val, wkd, &date))
- ADD_DATE(date.mm, date.dd, date.yy);
- }
-
- /* all other combinations of ordinal and day */
-
- else {
- /* set up loop boundaries for "wildcard" ordinals */
-
- val_first = ord == ORD_ALL || ord == ORD_ODD ? 1 :
- ord == ORD_EVEN ? 2 : val;
- val_last = ord == ORD_ALL || ord == ORD_ODD ? 5 :
- ord == ORD_EVEN ? 4 : val;
- val_incr = ord == ORD_ODD || ord == ORD_EVEN ? 2 : 1;
-
- for (mm = mon_first; mm <= mon_last; mm++)
- for (val = val_first; val <= val_last; val += val_incr)
- if ((dd = calc_day(val, wkd, mm)) != 0)
- ADD_DATE(mm, dd, curr_year);
- }
-
- TERM_DATES; /* terminate array with null entry */
- return PARSE_OK;
- }
-
-
- /*
- * parse_rel - parse a relative date spec (e.g. "Friday after fourth Thursday
- * in November", "2nd Saturday after first Friday in all"; return PARSE_OK if
- * line syntax valid, PARSE_INVLINE if not. Transform all dates that match
- * the base date to the appropriate day, month, and year.
- *
- * This calls parse_date() recursively in order to handle cases such as
- * "Friday after Tuesday before last day in all".
- */
- int
- #ifdef PROTOS
- parse_rel(int val, /* valid (positive) ordinal value */
- int wkd, /* valid weekday code - from get_weekday() */
- char **pword, /* pointer to word after weekday */
- int *ptype, /* return text type (holiday/non-holiday) */
- char ***pptext) /* return pointer to first word of text */
- #else
- parse_rel(val, wkd, pword, ptype, pptext)
- int val; /* valid (positive) ordinal value */
- int wkd; /* valid weekday code - from get_weekday() */
- char **pword; /* pointer to word after weekday */
- int *ptype; /* return text type (holiday/non-holiday) */
- char ***pptext; /* return pointer to first word of text */
- #endif
- {
- int prep, n, rtn, base_wkd, incr, (*pfcn)();
- DATE *pd;
-
- /* we have the weekday - now look for the preposition */
- if ((prep = get_prep(*pword++)) == PR_OTHER)
- return PARSE_INVLINE;
-
- /* get the base date */
- if ((rtn = parse_date(pword, ptype, pptext)) != PARSE_OK)
- return rtn;
-
- /* transform date array in place - note that the relative date may
- not be in the same month or even year */
-
- if (IS_WILD(wkd)) { /* wildcard for weekday name? */
- pfcn = pdatefcn[wkd - WILD_FIRST];
- incr = prep == PR_BEFORE || prep == PR_ON_BEFORE ? -1 : 1;
-
- for (pd = dates; pd->mm; pd++) {
- /* search for nearest matching date */
-
- if (prep == PR_BEFORE || prep == PR_AFTER) {
- pd->dd += incr;
- normalize(pd);
- }
- /* If NEAREST_INCR (cf. pcaldefs.h) is 1, Pcal will
- * disambiguate "nearest" in favor of the later date;
- * if -1, in favor of the earlier. "incr" will take
- * the values 1, -2, 3, -4, ... or -1, 2, -3, 4 ...
- * respectively.
- */
- if (prep == PR_NEAREST) {
- val = 1; /* ordinals meaningless here */
- incr = NEAREST_INCR;
- }
- n = val;
- while (!((*pfcn)(pd->mm, pd->dd, pd->yy) && --n == 0)) {
- pd->dd += incr;
- normalize(pd);
- if (prep == PR_NEAREST)
- incr -= (incr > 0) ? (2 * incr + 1) :
- (2 * incr - 1);
- }
- }
-
- } else { /* explicit weekday name */
- for (pd = dates; pd->mm; pd++) {
- /* calculate nearest matching date */
-
- base_wkd = calc_weekday(pd->mm, pd->dd, pd->yy);
- switch (prep) {
- case PR_BEFORE:
- case PR_ON_BEFORE:
- if (prep == PR_BEFORE || wkd != base_wkd)
- pd->dd -= 7 - (wkd - base_wkd + 7) % 7;
- pd->dd -= 7 * (val - 1);
- break;
-
- case PR_AFTER:
- case PR_ON_AFTER:
- if (prep == PR_AFTER || wkd != base_wkd)
- pd->dd += (wkd - base_wkd + 6) % 7 + 1;
- pd->dd += 7 * (val - 1);
- break;
-
- case PR_NEAREST:
- /* use closer of previous and next */
- val = wkd - base_wkd;
- pd->dd += (val > 3) ? (val - 7) :
- (val < -3) ? (val + 7) : val;
- break;
-
- default:
- return PARSE_INVLINE;
- break;
- }
-
- normalize(pd); /* adjust for month/year crossing */
- }
- }
-
- return PARSE_OK;
- }
-
-
- /*
- * parse_date - parse a date specification in any of its myriad forms; upon
- * return, array dates[] will contain a list of all the dates that matched,
- * terminated by a null entry. Also fill in the date type (holiday/non-
- * holiday) code and the pointer to the first word of text.
- */
- int
- #ifdef PROTOS
- parse_date(char **pword, /* first word to parse */
- int *ptype, /* return date type (holiday/non-holiday) */
- char ***pptext) /* return pointer to first word of text */
- #else
- parse_date(pword, ptype, pptext)
- char **pword; /* first word to parse */
- int *ptype; /* return date type (holiday/non-holiday) */
- char ***pptext; /* return pointer to first word of text */
- #endif
- {
- int mm, dd, yy;
- int token, n, v, ord, val, wkd, rtn;
- DATE *pdate;
- char *cp;
-
- pdate = dates;
-
- switch (token = date_type(*pword, &n, &v)) {
-
- case DT_MONTH: /* <month> dd */
- if (date_style != USA_DATES)
- return PARSE_INVLINE;
-
- if ((cp = *++pword) == NULL)
- return PARSE_INVLINE;
-
- ADD_DATE(n, atoi(cp), curr_year);
- TERM_DATES;
-
- break;
-
- case DT_DATE: /* mm/dd{/yy} | dd/mm{/yy} */
- n = split_date(*pword,
- date_style == USA_DATES ? &mm : &dd,
- date_style == USA_DATES ? &dd : &mm,
- &yy);
-
- if (n > 2) { /* year present? */
- if (yy < 100)
- yy += CENTURY;
- curr_year = yy; /* reset current year */
- }
-
- ADD_DATE(mm, dd, curr_year);
- TERM_DATES;
-
- break;
-
- case DT_EURDATE: /* dd [ <month> | "all" ] */
- if (date_style != EUR_DATES)
- return PARSE_INVLINE;
-
- dd = atoi(*pword);
-
- if (get_keywd(*++pword) == DT_ALL) {
- for (mm = JAN; mm <= DEC; mm++) /* wildcard */
- ADD_DATE(mm, dd, curr_year);
- }
- else { /* one month */
- if ((mm = get_month(*pword, TRUE, FALSE)) == NOT_MONTH)
- return PARSE_INVLINE;
-
- ADD_DATE(mm, dd, curr_year);
- }
-
- TERM_DATES;
- break;
-
- case DT_ALL: /* "all" <weekday> "in" [ <month> | "all" ] */
- /* or "all" <day>" */
-
- if ((cp = *(pword+1)) && (*(cp += strspn(cp, DIGITS)) == '\0' ||
- *cp == '*')) {
- dd = atoi(*++pword); /* "all" <day> */
- for (mm = JAN; mm <= DEC; mm++)
- ADD_DATE(mm, dd, curr_year);
- TERM_DATES;
- break; /* leave switch */
- }
-
- n = ORD_ALL; /* "all" <weekday> ... */
- v = 0;
- /* fall through */
-
- case DT_ORDINAL: /* <ordinal> <weekday> in [ <month> | "all" ] */
- /* or <ordinal> <weekday> <prep> <date> */
- ord = n;
- val = v;
- /* disambiguate above cases based on preposition */
- if (ord == ORD_POSNUM && pword[1] &&
- (get_prep(pword[2]) != PR_OTHER)) {
- if ((wkd = get_weekday(pword[1], TRUE)) == NOT_WEEKDAY)
- return PARSE_INVLINE;
- return parse_rel(val, wkd, pword += 2, ptype, pptext);
- }
- if ((rtn = parse_ord(ord, val, pword + 1)) != PARSE_OK)
- return rtn;
-
- pword += 3; /* last word of date */
- break;
-
- case DT_WEEKDAY: /* <weekday> <prep> <date> */
- wkd = n;
- /* parse_rel() calls parse_date() recursively */
- return parse_rel(1, wkd, ++pword, ptype, pptext);
- break;
-
- case DT_HOLIDAY: /* predefined holiday */
- /*
- * predefined holidays will either have a redefinition string
- * or a dispatch function - never both (cf. pcallang.h)
- */
- if (holidays[n].pfcn == NULL) {
- char redef[STRSIZ], *rwords[20], **pdum;
- int rtn, idum;
-
- /* tokenize local copy of redefinition string */
- strcpy(redef, holidays[n].def);
- (void) loadwords(rwords, redef);
-
- /* call parse_date() recursively to parse the
- * redefinition - if OK, drop through to fill in
- * real ptype and pptext from original string (this
- * works basically because the holiday is always
- * the last token before the text)
- */
- if ((rtn = parse_date(rwords, &idum, &pdum)) != PARSE_OK)
- return rtn;
- } else {
- /* holiday has a dispatch function - use it */
- pdate += (*holidays[n].pfcn)(pdate);
- TERM_DATES;
- }
- break;
-
- default:
- return PARSE_INVLINE;
- break;
- }
-
- /* at this point, pword points to the last component of the date;
- * fill in type code and pointer to following word (start of text)
- */
- *ptype = LASTCHAR(*pword) == '*' ? HOLIDAY_TEXT : DAY_TEXT;
- *pptext = ++pword;
-
- return PARSE_OK;
- }
-
-
- /*
- * parse - parse non-preprocessor lines in date file
- *
- * This routine parses "year", "opt", "note", and date entries in the date
- * file. It calls parse_date() to parse date entries (and enter the date(s)
- * matched in global array "dates"), and then calls enter_day_info() to
- * enter each date found (and its associated text) in the date file.
- *
- * N.B.: "inc" and other cpp-like lines are handled in read_datefile().
- *
- */
- int
- #ifdef PROTOS
- parse(char **pword, /* pointer to first word to parse */
- char *filename) /* name of file (for error messages) */
- #else
- parse(pword, filename)
- char **pword; /* pointer to first word to parse */
- char *filename; /* name of file (for error messages) */
- #endif
- {
- register char *cp;
- char **ptext;
- int mm, yy;
- int text_type, n, v, rtn, match, valid;
- int token;
- DATE *pd;
-
- /*
- * Get first field and call date_type() to decode it
- */
- cp = *pword;
-
- switch (token = date_type(cp, &n, &v)) {
-
- case DT_YEAR:
- if ((cp = *++pword) != NULL && (yy = atoi(cp)) > 0) {
- if (yy < 100)
- yy += CENTURY;
- curr_year = yy;
- return PARSE_OK;
- }
- return PARSE_INVLINE; /* year missing or non-numeric */
- break;
-
- case DT_OPT:
- if (!get_args(pword, P_OPT, filename, FALSE)) {
- usage(stderr, FALSE);
- exit(EXIT_FAILURE);
- }
- return PARSE_OK;
- break;
-
- case DT_NOTE:
- /* look for optional "/<n>" following keyword */
- n = (cp = strrchr(cp, '/')) ? atoi(++cp) : 0;
-
- if ((mm = get_month(*++pword, TRUE, TRUE)) == NOT_MONTH)
- return PARSE_INVLINE;
-
- if (mm == ALL_MONTHS || mm == ENTIRE_YEAR) { /* "note all"? */
- valid = FALSE; /* is at least one note box valid? */
- for (mm = JAN; mm <= DEC; mm++)
- valid |= enter_day_info(mm,
- note_day(mm, n, curr_year),
- curr_year, NOTE_TEXT,
- pword+1) == PARSE_OK;
- return valid ? PARSE_OK : PARSE_NOMATCH;
- }
-
- return enter_day_info(mm, note_day(mm, n, curr_year),
- curr_year, NOTE_TEXT, pword+1);
- break;
-
- case DT_OTHER: /* unrecognized token */
- return PARSE_INVLINE;
- break;
-
- /* assume anything else is a date */
-
- default:
- if ((rtn = parse_date(pword, &text_type, &ptext)) == PARSE_OK) {
- match = FALSE; /* is at least one date valid? */
- for (pd = dates; pd->mm; pd++)
- match |= enter_day_info(pd->mm, pd->dd, pd->yy,
- text_type, ptext) == PARSE_OK;
- rtn = match ? PARSE_OK : PARSE_NOMATCH;
- }
- return rtn;
- break;
-
- }
- }
-